package com.comut.crud.dao;

import com.comut.crud.pojo.mobile.VehicleDetail;
import com.comut.crud.pojo.mobile.VehicleInfo;
import com.comut.lib.connection.MongoDBConnectionManager;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.jms.ConnectionFactory;
import javax.jms.DeliveryMode;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.naming.InitialContext;

/**
 * Issue: IFL-1566
 *
 * @author Paulo Fournier
 */
public class VehicleDao {

    private final Connection _connection;
    private final MongoDBConnectionManager _mongodb;

    /**
     * Set SQL and Mongp connection instances
     * @param connection
     * @param mongodb
     */
    public VehicleDao(Connection connection, MongoDBConnectionManager mongodb) {
        this._connection = connection;
        this._mongodb = mongodb;
    }

    /**
     * Get the list of vehicles that belong to a user
     * @param userId The user id
     * @return VehicleInfo[] The list of vehicles
     */
    public VehicleInfo[] getVehiclesForUserId(int userId) {
        HashMap<Integer, VehicleInfo> vehicles = this.getVehiclesForUserIdSQL(userId);

        try {
            InitialContext initialContext = new InitialContext();
            ConnectionFactory connectionFactory = (ConnectionFactory) initialContext.lookup("/ConnectionFactory");
            javax.jms.Connection jmsConnection = (javax.jms.Connection) connectionFactory.createConnection();
            jmsConnection.start();

            Session jmsSession = (Session) jmsConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            String taskId = com.comut.crud.util.Util.generateUUID();
            int numberSentMessages = sendJMSMessages(initialContext, jmsSession, vehicles, taskId);
            waitJMSMessageReplies(initialContext, jmsSession, numberSentMessages, vehicles, taskId);

            jmsSession.close();
            jmsConnection.close();
        } catch (Exception ex) {
            Logger.getLogger(VehicleDao.class.getName()).log(Level.SEVERE, null, ex);
        }

        VehicleInfo[] ret = new VehicleInfo[vehicles.size()];
        return vehicles.values().toArray(ret);
    }

    /**
     * Get the list of vehicles that belong to a user from SQL (SQL Permissions)
     * @param userId The user id
     * @return HashMap The list of vehicles
     */
    private HashMap<Integer, VehicleInfo> getVehiclesForUserIdSQL(int userId) {
        HashMap<Integer, VehicleInfo> vehicles = new HashMap();
        CallableStatement statement = null;
        ResultSet resultSet = null;
        String query = "{call [crud].[usp_getVehicles](?)}";

        try {
            statement = _connection.prepareCall(query);
            statement.setInt(1, userId);
            resultSet = statement.executeQuery();

            while (resultSet.next()) {
                VehicleInfo vehicle = new VehicleInfo();
                int idVehicle = resultSet.getInt("id");
                vehicle.setId(resultSet.getInt("id"));
                vehicle.setVehicleIcon(resultSet.getInt("vehicleIcon"));
                vehicle.setReference(resultSet.getString("reference"));
                vehicle.setCarPlate(resultSet.getString("carPlate"));
                vehicle.setTag(resultSet.getString("tag"));

                if (vehicles.containsKey(idVehicle) == false) {
                    vehicles.put(idVehicle, vehicle);
                } else {
                    VehicleInfo vi = vehicles.get(idVehicle);
                    if (vehicle.getTag() != null) {
                        vi.setTag(String.format("%s %s", vi.getTag(), vehicle.getTag()));
                    }
                }
            }
        } catch (Exception ex) {
            java.util.logging.Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
        } finally {
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException ex) {
                    java.util.logging.Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
                }
            }
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException ex) {
                    java.util.logging.Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
                }
            }
        }

        return vehicles;
    }

    /**
     * Send a Java Message Service with the driver for a post process queue
     * @param initialContext The message inicial context
     * @param jmsSession The java message service session
     * @param vehicles The list of vehicles
     * @param taskId The task unique id
     * @return int The sent result
     */
    private int sendJMSMessages(InitialContext initialContext, Session jmsSession, HashMap<Integer, VehicleInfo> vehicles, String taskId) {
        int nrMsgs = 0;

        try {
            Queue vehicleInfoQueue = (Queue) initialContext.lookup("/queue/VehicleInfoQueue");
            MessageProducer jmsMessageProducer = jmsSession.createProducer(vehicleInfoQueue);

            int maxPriority = 9;
            int messageTimeToLiveMilliseconds = 10000;

            for (VehicleInfo vehicleInfo : vehicles.values()) {
                TextMessage message = jmsSession.createTextMessage(String.format("Vehicle id:%d", vehicleInfo.getId()));
                message.setIntProperty("vehicleId", vehicleInfo.getId());
                message.setLongProperty("milliTime", System.currentTimeMillis());
                message.setStringProperty("taskId", taskId);
                jmsMessageProducer.send(message, DeliveryMode.NON_PERSISTENT, maxPriority, messageTimeToLiveMilliseconds);
                nrMsgs++;
            }
            jmsMessageProducer.close();

        } catch (Exception ex) {
            Logger.getLogger(VehicleDao.class.getName()).log(Level.INFO, "Error sending JMS message VehicleDAO");
        }

        return nrMsgs;
    }

    /**
     * Receive a Java Message Service with the driver's information (JSM Queue reply)
     * @param initialContext The message inicial context
     * @param jmsSession The java message service session
     * @param numberSentMessages The number of messages to receive
     * @param vehicles The list of vehicles to be updated
     * @param taskId The task unique id
     */
    private void waitJMSMessageReplies(InitialContext initialContext, Session jmsSession, int numberSentMessages, HashMap<Integer, VehicleInfo> vehicles, String taskId) {
        MessageConsumer consumer = null;
        Queue vehicleInfoReplyQueue = null;
        String messageSelector = String.format("taskId = '%s' ", taskId);
        java.util.logging.Logger.getLogger(this.getClass().getName()).log(Level.INFO, messageSelector);

        try {
            vehicleInfoReplyQueue = (Queue) initialContext.lookup("/queue/VehicleInfoReplyQueue");
            // gets replies for taskId
            consumer = jmsSession.createConsumer(vehicleInfoReplyQueue, messageSelector);
            int timeoutReceiveMilliseconds = 15000 / numberSentMessages;

            for (int msgIndex = 0; msgIndex < numberSentMessages; msgIndex++) {
                Message reply = consumer.receive(timeoutReceiveMilliseconds);

                if (reply != null) {
                    int vehicleId = reply.getIntProperty("vehicleId");
                    int gsp = reply.getIntProperty("gsp");
                    int hdg = reply.getIntProperty("hdg");
                    double lat = reply.getDoubleProperty("lat");
                    double lon = reply.getDoubleProperty("lon");
                    String fractal = reply.getStringProperty("fractal");
                    boolean hasTemperature = reply.getBooleanProperty("hasTemperature");
                    Integer tp1 = null;
                    Integer tp2 = null;
                    Integer tp3 = null;
                    Integer tp4 = null;

                    Integer[] temperatures = new Integer[4];

                    if (hasTemperature) {

                        if (reply.propertyExists("tp1")) {
                            temperatures[0] = new Integer(reply.getIntProperty("tp1"));
                        }

                        if (reply.propertyExists("tp2")) {
                            temperatures[1] = new Integer(reply.getIntProperty("tp2"));
                        }

                        if (reply.propertyExists("tp3")) {
                            temperatures[2] = new Integer(reply.getIntProperty("tp3"));
                        }

                        if (reply.propertyExists("tp4")) {
                            temperatures[3] = new Integer(reply.getIntProperty("tp4"));
                        }
                    }

                    VehicleInfo vi = vehicles.get(vehicleId);
                    vi.setLocal(fractal);
                    vi.setSpeed(gsp);
                    vi.setRoute(hdg);
                    vi.setLoadTemperature(temperatures);
                }
            }

        } catch (Exception ex) {
            Logger.getLogger(VehicleDao.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            if (consumer != null) {
                try {
                    consumer.close();
                } catch (JMSException ex) {
                    Logger.getLogger(VehicleDao.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    }

    /**
	 * Get the detailed information of a vehicle
     * Issue: IFL-1567
     * @param userId The user id
     * @param vehicleId The vehicle id
     * @return VehicleDetail The vehicle detail
     */
    public VehicleDetail getVehicleDetail(int userId, int vehicleId) {
        VehicleDetail vehicle = getVehicleDetailSQL(userId, vehicleId);

        if (vehicle != null) {
            // mongo data 
            try {
                MongoDBConnectionManager mongoDB = MongoDBConnectionManager.instanceFromSystemProperties();
                Mongo mongoConnection = mongoDB.getConnection();
                DB mongoDatabase = mongoDB.getDB(mongoConnection);


                fillFuelOdometerInfo(mongoDatabase, vehicleId, vehicle);
                fillToll(mongoDatabase, vehicleId, vehicle);

                mongoDB.freeConnection(mongoConnection, mongoDatabase);

            } catch (Exception ex) {
                java.util.logging.Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
            }
        }

        return vehicle;
    }

    /**
	 * Get detail from SQL
     * Issue: IFL-1567
     * @param userId The user id
     * @param vehicleId The vehicle id
     * @return VehicleDetaill The vehicle detail
     */
    private VehicleDetail getVehicleDetailSQL(int userId, int vehicleId) {
        VehicleDetail ret = null;
        CallableStatement statement = null;
        ResultSet resultSet = null;

        String query = "{call [crud].[usp_getVehicleDetail](?,?)}";

        try {
            statement = _connection.prepareCall(query);
            statement.setInt(1, userId);
            statement.setInt(2, vehicleId);
            resultSet = statement.executeQuery();

            if (resultSet.next()) {
                ret = new VehicleDetail();
                int idVehicle = resultSet.getInt("id");
                ret.setId(idVehicle);
                ret.setBrand(resultSet.getString("brand"));
                ret.setModel(resultSet.getString("model"));
                ret.setFirmware(resultSet.getString("firmware"));
                ret.setSerialNumber(resultSet.getString("serialNumber"));
            }
        } catch (Exception ex) {
            java.util.logging.Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
        } finally {
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException ex) {
                    java.util.logging.Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
                }
            }
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException ex) {
                    java.util.logging.Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
                }
            }
        }

        return ret;
    }

    /**
	 * Get the toll information of the vehicle from MongoDB
     * Issue: IFL-1567
     * @param db The database instance
     * @param vehicleId The vehicle ID
     * @param vehicle The vehicle detail
     */
    private void fillToll(DB db, int vehicleId, VehicleDetail vehicle) {
        DBCollection events = db.getCollection("events");
        // db.events.find({mid:2750,eid:{$in:[3839,3840]}},{prc:1}).sort({tmx:-1}).limit(1).pretty()
        BasicDBObject fieldMobileInfo = new BasicDBObject();
        fieldMobileInfo.put("_id", 1);
        fieldMobileInfo.put("prc", 1); //double

        BasicDBObject queryMongo = new BasicDBObject();
        queryMongo.put("mid", vehicleId);
        BasicDBList causesIds = new BasicDBList();
        causesIds.add(3839);
        causesIds.add(3840);
        BasicDBObject inClause = new BasicDBObject();
        inClause.put("$in", causesIds);
        queryMongo.put("eid", inClause);
        DBCursor cursor = events.find(queryMongo, fieldMobileInfo);
        BasicDBObject sort = new BasicDBObject();
        sort.put("tmx", -1);
        cursor.sort(sort);
        cursor.limit(1);
        List<DBObject> results = cursor.toArray();
        double totalCostToll = 0;

        for (DBObject result : results) {
            totalCostToll = Double.parseDouble(result.get("prc").toString());
        }

        vehicle.setTotalCostTolls(totalCostToll);
    }

    /**
     * Get canbus information from MongoDB
	 * Issue: IFL-1567
     * @param db The database instance
     * @param vehicleId The vehicle ID
     * @param vehicle The vehicle detail
     */
    private void fillFuelOdometerInfo(DB db, int vehicleId, VehicleDetail vehicle) {
        DBCollection mobileInfoCollection = db.getCollection("mobile_info");
        //db.mobile_info.find({mid:2750},{"exd.fcl":1,"can.flv":1,"eco.flv":1,"can.ckm":1,"pos.gkm":1}).pretty()
        BasicDBObject fieldMobileInfo = new BasicDBObject();
        fieldMobileInfo.put("_id", 1);
        fieldMobileInfo.put("exd.fcl", 1); //double
        fieldMobileInfo.put("can.flv", 1); //double
        fieldMobileInfo.put("eco.flv", 1); //double
        fieldMobileInfo.put("eco.ckm", 1); //double
        fieldMobileInfo.put("can.ckm", 1); //double 
        fieldMobileInfo.put("pos.gkm", 1); //int

        BasicDBObject queryMongo = new BasicDBObject();
        queryMongo.put("mid", vehicleId);
        DBObject result = mobileInfoCollection.findOne(queryMongo);

        double odometer = 0;
        double fuelConsumption = 0;
        double fuelLevel = 0;

        if (result != null) {

            // based on the edt field, the data can come in the can or eco field
            if (result.containsField("eco") || result.containsField("can")) {
                // has canbus/ecodrive data                
                DBObject eco = (DBObject) result.get("eco");
                DBObject can = (DBObject) result.get("can");
                DBObject exd = (DBObject) result.get("exd");

                if (eco.containsField("ckm")) {
                    odometer = Double.parseDouble(eco.get("ckm").toString());
                }

                if (can.containsField("ckm")) {
                    odometer = Double.parseDouble(can.get("ckm").toString());
                }

                if (eco.containsField("flv")) {
                    fuelLevel = Double.parseDouble(eco.get("flv").toString());
                }

                if (can.containsField("flv")) {
                    fuelLevel = Double.parseDouble(can.get("flv").toString());
                }

                if (exd.containsField("fcl")) {
                    fuelConsumption = Double.parseDouble(exd.get("fcl").toString());
                }

            } else {
                DBObject pos = (DBObject) result.get("pos");
                odometer = Double.parseDouble(pos.get("gkm").toString());
            }
        }

        vehicle.setFuelLevel(fuelLevel);
        vehicle.setFuelConsumption(fuelConsumption);
        vehicle.setOdometer(odometer);
    }
}
